//------------------------------------------------------------------------------
// <copyright file="XmlAttribute.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------

namespace System.Xml {
    using System;
    using System.Xml.Schema;
    using System.Xml.XPath;
    using System.Diagnostics;

    // Represents an attribute of the XMLElement object. Valid and default
    // values for the attribute are defined in a DTD or schema.
    public class XmlAttribute : XmlNode {
        XmlName name;
        XmlLinkedNode lastChild;
        
        internal XmlAttribute( XmlName name, XmlDocument doc ): base( doc ) {
            Debug.Assert(name!=null);
            Debug.Assert(doc!=null);
            this.parentNode = null;
            if ( !doc.IsLoading ) {
                XmlDocument.CheckName( name.Prefix );
                XmlDocument.CheckName( name.LocalName );
            }
            if (name.LocalName.Length == 0)
                throw new ArgumentException(Res.GetString(Res.Xdom_Attr_Name));
            this.name = name;
        }

        internal int LocalNameHash {
            get { return name.HashCode; }
        }

        protected internal XmlAttribute( string prefix, string localName, string namespaceURI, XmlDocument doc )
        : this(doc.AddAttrXmlName(prefix, localName, namespaceURI, null), doc) {
        }

        internal XmlName XmlName {
            get { return name; }
            set { name = value; }
        }

        // Creates a duplicate of this node.
        public override XmlNode CloneNode(bool deep) {
            // CloneNode for attributes is deep irrespective of parameter 'deep' value     
            Debug.Assert( OwnerDocument != null );
            XmlDocument doc = OwnerDocument;
            XmlAttribute attr = doc.CreateAttribute( Prefix, LocalName, NamespaceURI );
            attr.CopyChildren( doc, this, true );
            return attr;
        }

        // Gets the parent of this node (for nodes that can have parents).
        public override XmlNode ParentNode {
            get { return null;}
        }

        // Gets the name of the node.
        public override String Name { 
            get { return name.Name;}
        }

        // Gets the name of the node without the namespace prefix.
        public override String LocalName { 
            get { return name.LocalName;}
        }

        // Gets the namespace URI of this node.
        public override String NamespaceURI { 
            get { return name.NamespaceURI;} 
        }

        // Gets or sets the namespace prefix of this node.
        public override String Prefix { 
            get { return name.Prefix;}
            set { name = name.OwnerDocument.AddAttrXmlName( value, LocalName, NamespaceURI, SchemaInfo ); }
        }

        // Gets the type of the current node.
        public override XmlNodeType NodeType {
            get { return XmlNodeType.Attribute;}
        }

        // Gets the XmlDocument that contains this node.
        public override XmlDocument OwnerDocument { 
            get { 
                return name.OwnerDocument;
            }
        }

        // Gets or sets the value of the node.
        public override String Value { 
            get { return InnerText; }
            set { InnerText = value; } //use InnerText which has perf optimization
        }

        public override IXmlSchemaInfo SchemaInfo {
            get {
                return name;
            }
        }

        public override String InnerText {
            set {
                if (PrepareOwnerElementInElementIdAttrMap()) {
                    string innerText = base.InnerText;
                    base.InnerText = value;
                    ResetOwnerElementInElementIdAttrMap(innerText);
                }
                else {
                    base.InnerText = value;
                }
            }
        }

        internal bool PrepareOwnerElementInElementIdAttrMap() {
            XmlDocument ownerDocument = OwnerDocument; 
            if (ownerDocument.DtdSchemaInfo != null) { // DTD exists
                XmlElement ownerElement = OwnerElement;
                if (ownerElement != null) {
                    return ownerElement.Attributes.PrepareParentInElementIdAttrMap(Prefix, LocalName);
                }
            }
            return false;
        }

        internal void ResetOwnerElementInElementIdAttrMap(string oldInnerText) {
            XmlElement ownerElement = OwnerElement;
            if (ownerElement != null) {
                ownerElement.Attributes.ResetParentInElementIdAttrMap(oldInnerText, InnerText);
            }
        }

        internal override bool IsContainer {
            get { return true;}
        }

        //the function is provided only at Load time to speed up Load process
        internal override XmlNode AppendChildForLoad(XmlNode newChild, XmlDocument doc) {
            XmlNodeChangedEventArgs args = doc.GetInsertEventArgsForLoad( newChild, this );

            if (args != null)
                doc.BeforeEvent( args );

            XmlLinkedNode newNode = (XmlLinkedNode)newChild;

            if (lastChild == null) { // if LastNode == null
                newNode.next = newNode;
                lastChild = newNode;
                newNode.SetParentForLoad(this);
            }
            else {
                XmlLinkedNode refNode = lastChild; // refNode = LastNode;
                newNode.next = refNode.next;
                refNode.next = newNode;
                lastChild = newNode; // LastNode = newNode;
                if (refNode.IsText
                    && newNode.IsText) {
                    NestTextNodes(refNode, newNode);
                }
                else {
                    newNode.SetParentForLoad(this);
                }
            }

            if (args != null)
                doc.AfterEvent( args );

            return newNode;
        }

        internal override XmlLinkedNode LastNode {
            get { return lastChild;}
            set { lastChild = value;}
        }
        
        internal override bool IsValidChildType( XmlNodeType type ) {
            return(type == XmlNodeType.Text) || (type == XmlNodeType.EntityReference);
        }

        // Gets a value indicating whether the value was explicitly set.
        public virtual bool Specified { 
            get { return true;}
        }

        public override XmlNode InsertBefore(XmlNode newChild, XmlNode refChild) {
            XmlNode node;
            if (PrepareOwnerElementInElementIdAttrMap()) {
                string innerText = InnerText;
                node = base.InsertBefore(newChild, refChild);
                ResetOwnerElementInElementIdAttrMap(innerText);
            }
            else {
                node = base.InsertBefore(newChild, refChild);
            }
            return node;
        }

        public override XmlNode InsertAfter(XmlNode newChild, XmlNode refChild) {
            XmlNode node;
            if (PrepareOwnerElementInElementIdAttrMap()) {
                string innerText = InnerText;
                node = base.InsertAfter(newChild, refChild);
                ResetOwnerElementInElementIdAttrMap(innerText);
            }
            else {
                node = base.InsertAfter(newChild, refChild);
            }
            return node;
        }

        public override XmlNode ReplaceChild(XmlNode newChild, XmlNode oldChild) {
            XmlNode node;
            if (PrepareOwnerElementInElementIdAttrMap()) {
                string innerText = InnerText;
                node = base.ReplaceChild(newChild, oldChild);
                ResetOwnerElementInElementIdAttrMap(innerText);
            }
            else {
                node = base.ReplaceChild(newChild, oldChild);
            }
            return node;
        }

        public override XmlNode RemoveChild(XmlNode oldChild) {
            XmlNode node;
            if (PrepareOwnerElementInElementIdAttrMap()) {
                string innerText = InnerText;
                node = base.RemoveChild(oldChild);
                ResetOwnerElementInElementIdAttrMap(innerText);
            }
            else {
                node = base.RemoveChild(oldChild);
            }
            return node;
        }

        public override XmlNode PrependChild(XmlNode newChild) {
            XmlNode node;
            if (PrepareOwnerElementInElementIdAttrMap()) {
                string innerText = InnerText;
                node = base.PrependChild(newChild);
                ResetOwnerElementInElementIdAttrMap(innerText);
            }
            else {
                node = base.PrependChild(newChild);
            }
            return node;
        }

        public override XmlNode AppendChild(XmlNode newChild) {
            XmlNode node;
            if (PrepareOwnerElementInElementIdAttrMap()) {
                string innerText = InnerText;
                node = base.AppendChild(newChild);
                ResetOwnerElementInElementIdAttrMap(innerText);
            }
            else {
                node = base.AppendChild(newChild);
            }
            return node;
        }

        // DOM Level 2

        // Gets the XmlElement node that contains this attribute.
        public virtual XmlElement OwnerElement { 
            get { 
                return parentNode as XmlElement;
            }
        }

        // Gets or sets the markup representing just the children of this node.
        public override string InnerXml {
            set {
                RemoveAll();
                XmlLoader loader = new XmlLoader();
                loader.LoadInnerXmlAttribute( this, value );
            }
        }

        // Saves the node to the specified XmlWriter.
        public override void WriteTo(XmlWriter w) {
            w.WriteStartAttribute( Prefix, LocalName, NamespaceURI );
            WriteContentTo(w);
            w.WriteEndAttribute();
        }

        // Saves all the children of the node to the specified XmlWriter.
        public override void WriteContentTo(XmlWriter w) {
            for (XmlNode node = FirstChild; node != null; node = node.NextSibling) {
                node.WriteTo(w);
            }
        }

        public override String BaseURI {
            get {
                if ( OwnerElement != null )
                    return OwnerElement.BaseURI;
                return String.Empty;
            }
        }

        internal override void SetParent(XmlNode node) {
            this.parentNode = node;
        }
        
        internal override XmlSpace XmlSpace {
            get {
                if ( OwnerElement != null )
                    return OwnerElement.XmlSpace;
                return XmlSpace.None;
            }
        }

        internal override String XmlLang {
            get {
                if ( OwnerElement != null )
                    return OwnerElement.XmlLang;
                return String.Empty;
            }
        }
        internal override XPathNodeType XPNodeType { 
            get {
                if (IsNamespace) {
                    return XPathNodeType.Namespace; 
                }
                return XPathNodeType.Attribute;
            }
        }

        internal override string XPLocalName { 
            get {
                if (name.Prefix.Length == 0 && name.LocalName == "xmlns") return string.Empty;
                return name.LocalName;
            }
        }

        internal bool IsNamespace {
            get {
                return Ref.Equal(name.NamespaceURI, name.OwnerDocument.strReservedXmlns);
            }
        }
    }
}
